#region Using directives.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Design;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Text;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.Design;
using System.Web.UI.Design.WebControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
#endregion
using BizElements.BusinessLayer;

namespace BizElements.Web
{
    /// <summary>
    /// Displays a collection of <see cref="ICommand"/> objects as <see cref="Button"/>, <see cref="ImageButton"/> 
    /// or <see cref="TemplateButton"/> controls.
    /// </summary>
    [ToolboxData("<{0}:CommandToolBar runat=server></{0}:CommandToolBar>")]
    [Designer(typeof(CommandToolBarDesigner))]
    public class CommandToolBar : WebControl, INamingContainer, ICommandDisplay
    {
        #region Fields.
        
        CommandGroupStyle commandHierarchy = new CommandGroupStyle();
        IList<ICommand> commands = new List<ICommand>();
        IActor actor;
        IMessageFormatter formatter;
        bool displayReasonForDisabledItems = true;
        ButtonType buttonType = ButtonType.Button;
        bool autoDetectButtonType = true;
        string buttonCssClass;
        string buttonSkinID;        
        string imageButtonCssClass;
        string imageButtonSkinID;        
        Orientation orientation;
        string templateButtonCssClass;
        string templateButtonDefinition;
        string templateButtonArguments;
        string templateButtonScript;
        string templateButtonScriptIncludeUrls;
        string templateButtonSkinID;        
        bool horizontalSecurityEnabled = true;
        bool ajaxEnabled;

        #endregion

        #region Child controls.

        Table tblContainer = new Table();

        // Three textboxes are a part of update panel.
        UpdatePanel atlasPanel = new UpdatePanel();
        TextBox txtDisabledItems = new TextBox();
        TextBox txtDisabledToolTips = new TextBox();
        TextBox txtHiddenItems = new TextBox();

        #endregion

        #region CTor and CreateChildControls.

        /// <summary>
        /// Initializes a new instance of the <b>CommandToolBar</b> class.
        /// </summary>
        public CommandToolBar()
        {
            this.txtDisabledItems.Style["display"] = "none";
            this.txtDisabledToolTips.Style["display"] = "none";
            this.txtHiddenItems.Style["display"] = "none";

            this.atlasPanel.ChildrenAsTriggers = true;
            this.atlasPanel.UpdateMode = UpdatePanelUpdateMode.Always;
        }

        /// <summary>
        /// Called by the ASP.NET page framework to notify server controls that use composition-based 
        /// implementation to create any child controls they contain in preparation for posting back or rendering. 
        /// </summary>
        protected override void CreateChildControls()
        {
            // Command controls are not created in this method because this method may be invoked
            // before the actor, command collection and hierarchy have been provided.
            this.Controls.Clear();
            this.Controls.Add(this.tblContainer);
            EnsureEmptyControlContainer();

            this.ajaxEnabled = (ScriptManager.GetCurrent(this.Page) != null);
            if (this.AjaxEnabled)
            {
                this.Controls.Add(this.atlasPanel);
                this.atlasPanel.ContentTemplateContainer.Controls.Add(this.txtDisabledItems);
                this.atlasPanel.ContentTemplateContainer.Controls.Add(this.txtDisabledToolTips);
                this.atlasPanel.ContentTemplateContainer.Controls.Add(this.txtHiddenItems);
            }
            else
            {
                this.Controls.Add(this.txtDisabledItems);
                this.Controls.Add(this.txtDisabledToolTips);
                this.Controls.Add(this.txtHiddenItems);
            }
        }

        private void EnsureEmptyControlContainer()
        {
            this.tblContainer.Rows.Clear();
            this.tblContainer.Rows.Add(new TableRow());
            this.tblContainer.Rows[0].Cells.Add(new TableCell());
        }

        #endregion

        #region OnInit. RegisterClientScriptResource.

        /// <summary>
        /// Raises the Init event. 
        /// </summary>
        /// <param name="e">An EventArgs object that contains the event data.</param>
        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);
            Page.ClientScript.RegisterClientScriptResource(this.GetType(), "BizElements.Web.CommandToolBar.js");
        }

        #endregion

        #region Button IDs, enable/disable.

        // Indices of commands which are available to actor, ie. which pass vertical security checks.
        List<int> availableCmdIndices = new List<int>();

        private string GetButtonID(ICommand cmd)
        {
            return "n" + this.Commands.IndexOf(cmd);
        }

        private Control GetButton(ICommand cmd)
        {
            string id = GetButtonID(cmd);
            return FindControl(id);
        }

        private void ResetDisabledItems()
        {
            // ';' indicates to client-side script that async postback has completed but no items should be disabled.
            this.txtDisabledItems.Text = ";";
            this.txtDisabledToolTips.Text = ";";
        }

        private void ResetHiddenItems()
        {
            // ';' indicates to client-side script that async postback has completed but no items should be hidden.
            this.txtHiddenItems.Text = ";";
        }

        private void DisableCommand(ICommand cmd, string tooltip)
        {
            Control btn = GetButton(cmd);
            if (btn != null)
            {
                this.txtDisabledItems.Text += btn.ClientID + ";";
                tooltip = (tooltip != null) ? tooltip.Replace(';', ',') : "";
                this.txtDisabledToolTips.Text += tooltip + ";";
            }
        }

        private void HideCommand(ICommand cmd)
        {
            Control btn = GetButton(cmd);
            if (btn != null)
                this.txtHiddenItems.Text += btn.ClientID + ";";
        }

        #endregion

        #region CreateCommandControls.

        private void CreateCommandControls()
        {
            List<CommandStyle> commandsToBeDisplayed = FlattenCommandHierarchy(this.commandHierarchy);
            foreach (CommandStyle cmdStyle in commandsToBeDisplayed)
            {
                IWebCommand cmd = GetCommand(this.commands, cmdStyle.Key) as IWebCommand;
                if (cmd == null)
                    continue;

                bool obeysMinSize = (this.MinParameterSetSize >= 0) ? cmd.MinParameterSetSize >= this.MinParameterSetSize : true;
                bool obeysMaxSize = (this.MaxParameterSetSize >= 0) ? cmd.MaxParameterSetSize <= this.MaxParameterSetSize : true;
                if (!obeysMinSize || !obeysMaxSize)
                    continue;

                bool isUnavailable = (this.Actor != null && cmd.IsAvailableToActor(this.Actor).HasBrokenRules);
                bool dontRender = isUnavailable && (cmdStyle.UnavailableStyle == CommandStyle.DisableStyle.Automatic
                    || cmdStyle.UnavailableStyle == CommandStyle.DisableStyle.Hide);
                if (dontRender)
                    continue;

                TableRow container;
                if (this.Orientation == Orientation.Vertical)
                {
                    container = new TableRow();
                    this.tblContainer.Rows.Add(container);
                }
                else
                {
                    container = this.tblContainer.Rows[0];
                }

                TableCell cell = new TableCell();
                container.Cells.Add(cell);
                Control ctl = CreateCommandControl(cmd, cmdStyle);
                ctl.ID = GetButtonID(cmd);
                cell.Controls.Add(ctl);

                bool disable = isUnavailable && (cmdStyle.UnavailableStyle == CommandStyle.DisableStyle.Disable);
                if (disable)
                    cell.Enabled = false;

                if (!isUnavailable)
                    this.availableCmdIndices.Add(this.Commands.IndexOf(cmd));
            }
        }

        private Control CreateCommandControl(IWebCommand cmd, CommandStyle style)
        {
            if (cmd == null)
                throw new ArgumentNullException("cmd", "An instance of IWebCommand is required.");

            if (style == null)
                throw new ArgumentNullException("style", "An instance of CommandStyle is required.");

            ButtonType btnType = this.ButtonType;
            if (this.AutoDetermineButtonType)
            {
                ButtonType? autoType = null;
                if (style.ExtendedProperties.Properties.ContainsKey("ButtonType"))
                {
                    string specifiedButtonType = Convert.ToString(style.ExtendedProperties.Properties["ButtonType"]);
                    autoType = NumberParser.ParseEnum<ButtonType>(specifiedButtonType);
                }

                if (autoType == null && style.ExtendedProperties.Properties.ContainsKey("Template"))
                {
                    string specifiedTemplate = Convert.ToString(style.ExtendedProperties.Properties["Template"]);
                    if (!string.IsNullOrEmpty(specifiedTemplate))
                        autoType = ButtonType.TemplateButton;
                }

                if (autoType == null && style.ExtendedProperties.Properties.ContainsKey("ImageUrl"))
                {
                    string specifiedImageUrl = Convert.ToString(style.ExtendedProperties.Properties["ImageUrl"]);
                    if (!string.IsNullOrEmpty(specifiedImageUrl))
                        autoType = ButtonType.ImageButton;
                }

                if (autoType != null)
                    btnType = autoType.Value;
            }

            return CreateSpecifiedButton(btnType, cmd, style);
        }

        private Control CreateSpecifiedButton(ButtonType type, IWebCommand cmd, CommandStyle style)
        {
            switch (type)
            {
                case ButtonType.TemplateButton:
                    return CreateTemplateButton(cmd, style);
                case ButtonType.ImageButton:
                    return CreateImageButton(cmd, style);
                default:
                    return CreateButton(cmd, style);
            }
        }

        private Control CreateButton(IWebCommand cmd, CommandStyle style)
        {
            Button btn = new Button();
            if (!string.IsNullOrEmpty(this.ButtonSkinID))
            {
                btn.SkinID = this.ButtonSkinID;
                btn.ApplyStyleSheetSkin(this.Page);
            }

            btn.Text = (style.Text != null) ? style.Text : cmd.Text;
            btn.ToolTip = (style.Description != null) ? style.Description : btn.Text;
            btn.Attributes["onclick"] = cmd.ExecuteOnClient + "; return false; ";
            btn.CssClass = this.ButtonCssClass;
            style.ExtendedProperties.WriteToObject(btn, true);

            return btn;
        }

        private Control CreateImageButton(IWebCommand cmd, CommandStyle style)
        {
            ImageButton btn = new ImageButton();
            if (!string.IsNullOrEmpty(this.ImageButtonSkinID))
            {
                btn.SkinID = this.ImageButtonSkinID;
                btn.ApplyStyleSheetSkin(this.Page);
            }

            if (style.Description != null)
                btn.ToolTip = style.Description;
            else if (style.Text != null)
                btn.ToolTip = style.Text;
            else
                btn.ToolTip = cmd.Text;

            btn.Attributes["onclick"] = cmd.ExecuteOnClient + "; return false; ";
            btn.CssClass = this.ImageButtonCssClass;
            style.ExtendedProperties.WriteToObject(btn, true);

            return btn;
        }

        private Control CreateTemplateButton(IWebCommand cmd, CommandStyle style)
        {
            TemplateButton btn = new TemplateButton();
            if (!string.IsNullOrEmpty(this.TemplateButtonSkinID))
            {
                btn.SkinID = this.TemplateButtonSkinID;
                btn.ApplyStyleSheetSkin(this.Page);
            }

            btn.Text = (style.Text != null) ? style.Text : cmd.Text;
            btn.ToolTip = (style.Description != null) ? style.Description : btn.Text;
            btn.Attributes["onclick"] = cmd.ExecuteOnClient + "; return false; ";
            btn.CssClass = this.TemplateCssClass;
            btn.Template = this.Template;
            btn.TemplateArguments = this.TemplateArguments;
            btn.ClientScriptBlock = this.TemplateScript;
            btn.ClientScriptIncludeUrls = this.TemplateScriptIncludeUrls;
            style.ExtendedProperties.WriteToObject(btn, true);

            return btn;
        }

        private static ICommand GetCommand(IList<ICommand> list, string key)
        {
            foreach (ICommand cmd in list)
            {
                if (cmd.Key == key)
                    return cmd;
            }

            return null;
        }

        private static List<CommandStyle> FlattenCommandHierarchy(CommandGroupStyle group)
        {
            List<CommandStyle> flatList = new List<CommandStyle>();
            foreach (CommandGroupItem node in group.Items)
            {
                if (node.Command != null)
                    flatList.Add(node.Command);
                else if (node.Group != null)
                    flatList.AddRange(FlattenCommandHierarchy(node.Group));
                else
                    throw new InvalidOperationException("Both Commmand and Group properties of CommandGroupItem object contain null references.");
            }

            return flatList;
        }

        #endregion

        #region Horizontal security.

        private void EnforceHorizontalSecurity()
        {
            ResetDisabledItems();
            ResetHiddenItems();
            if (!this.HorizontalSecurityEnabled || this.Actor == null)
                return;

            foreach (int cmdIdx in this.availableCmdIndices)
            {
                ICommand cmd = this.Commands[cmdIdx];

                CommandStyle style = this.CommandHierarchy.FindCommand(cmd.Key);
                bool dontEnforceSecurityForCurrentCommand = (style != null && style.NotGrantedStyle == CommandStyle.DisableStyle.None);
                if (dontEnforceSecurityForCurrentCommand)
                    continue;

                bool doesntAcceptParams = (cmd.MaxParameterSetSize < 1);
                if (doesntAcceptParams)
                    continue;

                // PrepareForExecution sets parameters and checks if it's safe to execute the command.
                bool cannotInvokeGrantExecuteMethod = !cmd.PrepareForExecution();
                bool recordCountValid = CheckRecordCountForCommand(cmd);
                if (!recordCountValid)
                {
                    // Disable. No tooltip. Record count is checked even if PrepareForExecution failed for some other reason.
                    DisableOrHideCommand(cmd, "");
                }

                if (cannotInvokeGrantExecuteMethod)
                    continue;

                if (cmd.Parameters.Count > 0)
                {
                    // Check grants. Disable if not granted. Broken rules in tooltip.
                    BusinessRuleCollection failedPermissions = cmd.GrantExecute(this.Actor).GetBrokenRules();
                    if (failedPermissions.HasBrokenRules)
                        DisableOrHideCommand(cmd, failedPermissions.ToString(this.Formatter));
                }
            }
        }

        private static bool CheckRecordCountForCommand(ICommand cmd)
        {
            bool cmdHasMinCount = (cmd.MinParameterSetSize >= 0);
            bool minCountViolated = (cmdHasMinCount && cmd.Parameters.Count < cmd.MinParameterSetSize);
            if (minCountViolated)
                return false;

            bool cmdHasMaxCout = (cmd.MaxParameterSetSize >= 0);
            bool maxCountViolated = (cmdHasMaxCout && cmd.Parameters.Count > cmd.MaxParameterSetSize);
            if (maxCountViolated)
                return false;

            return true;
        }

        private void DisableOrHideCommand(ICommand cmd, string failedPermissions)
        {
            CommandStyle style = this.CommandHierarchy.FindCommand(cmd.Key);
            CommandStyle.DisableStyle disableMode = (style != null) ? style.NotGrantedStyle : CommandStyle.DisableStyle.Automatic;
            switch (disableMode)
            {
                case CommandStyle.DisableStyle.Automatic:
                case CommandStyle.DisableStyle.Disable:
                    string reasonWhyItemIsDisabled = (this.DisplayReasonForDisabledItems) ? failedPermissions : "";
                    DisableCommand(cmd, reasonWhyItemIsDisabled);
                    break;

                case CommandStyle.DisableStyle.Hide:
                    HideCommand(cmd);
                    break;

                default:
                    // Leave enabled. Other components will enforce security once the command is selected.
                    break;
            }
        }

        #endregion

        #region OnPreRender, Render.

        const string monitorInIntervalsFunctionCall = "Biz_CommandToolBar_MonitorInIntervals('{0}', {1}, '{2}', '{3}', '{4}'); ";

        /// <summary>
        /// Raises the PreRender event.
        /// </summary>
        /// <param name="e">An EventArgs object that contains the event data.</param>
        protected override void OnPreRender(EventArgs e)
        {
            base.OnPreRender(e);
            EnsureChildControls();
            if (!this.DesignMode)
            {
                if (!string.IsNullOrEmpty(this.CommandHierarchySrc))
                {
                    string fileSystemPath = Page.Server.MapPath(this.CommandHierarchySrc);
                    this.CommandHierarchy.ReadXmlFile(fileSystemPath, CultureInfo.CurrentUICulture);
                }
            }

            bool autoCreateCommandItems = (this.AutoGenerateCommandHierarchy && this.CommandHierarchy.Items.Count == 0);
            if (autoCreateCommandItems)
                this.CommandHierarchy = new CommandGroupStyle(this.Commands, this.MinParameterSetSize, this.MaxParameterSetSize);

            // Only now are we sure that the actor, command collection and hierarchy have been provided.
            CreateCommandControls();
            EnforceHorizontalSecurity();

            // The contents of the textbox which contain the IDs of disabled items is constantly monitored in browser.
            if (this.HorizontalSecurityEnabled)
            {
                string monitorSecurity = string.Format(monitorInIntervalsFunctionCall, this.ClientID, this.Commands.Count, this.txtDisabledItems.ClientID, this.txtHiddenItems.ClientID, this.txtDisabledToolTips.ClientID);
                Page.ClientScript.RegisterStartupScript(this.GetType(), this.ClientID + "_Monitor", monitorSecurity, true);
            }
        }

        #endregion        

        #region GetDesignTimeHtml.

        internal string GetDesignTimeHtml()
        {
            System.IO.StringWriter strWriter = new System.IO.StringWriter();
            HtmlTextWriter htmWriter = new HtmlTextWriter(strWriter);
            EnsureChildControls();
            RenderControl(htmWriter);
            string designTimeHtml = strWriter.GetStringBuilder().ToString();
            return designTimeHtml;
        }

        #endregion

        #region Settings.

        /// <summary>
        /// Gets <see cref="ICommandDisplay"/> settings.
        /// </summary>
        /// <returns>A collection of key-value property pairs guaranteed to work only with 
        /// the <see cref="ICommandDisplay"/> implementation which created it.</returns>
        public virtual IDictionary<string, object> GetSettings()
        {
            Dictionary<string, object> settings = new Dictionary<string, object>();

            AddNonDefaultVal<Color>(settings, "BackColor", this.BackColor);
            AddNonDefaultVal<Color>(settings, "BorderColor", this.BorderColor);
            AddNonDefaultVal<BorderStyle>(settings, "BorderStyle", this.BorderStyle);
            AddNonDefaultVal<Unit>(settings, "BorderWidth", this.BorderWidth);
            AddNonDefaultString(settings, "CssClass", this.CssClass);
            settings.Add("Font", this.Font);
            AddNonDefaultVal<Unit>(settings, "Height", this.Height);
            AddNonDefaultVal<Unit>(settings, "Width", this.Width);

            AddNonDefaultString(settings, "BackImageUrl", this.BackImageUrl);
            AddNonDefaultPositiveInt(settings, "CellPadding", this.CellPadding);
            AddNonDefaultPositiveInt(settings, "CellSpacing", this.CellSpacing);
            AddNonDefaultVal<GridLines>(settings, "GridLines", this.GridLines);

            settings.Add("Orientation", this.Orientation);            
            settings.Add("ButtonType", this.ButtonType);
            settings.Add("AutoDetermineButtonType", this.AutoDetermineButtonType);
            AddNonDefaultString(settings, "ButtonCssClass", this.ButtonCssClass);
            AddNonDefaultString(settings, "ImageButtonCssClass", this.ImageButtonCssClass);
            AddNonDefaultString(settings, "TemplateCssClass", this.TemplateCssClass);
            AddNonDefaultString(settings, "Template", this.Template);
            AddNonDefaultString(settings, "TemplateArguments", this.TemplateArguments);
            AddNonDefaultString(settings, "TemplateScript", this.TemplateScript);
            AddNonDefaultString(settings, "TemplateScriptIncludeUrls", this.TemplateScriptIncludeUrls);

            if (!this.AutoGenerateCommandHierarchy)
                settings.Add("AutoGenerateCommandHierarchy", this.AutoGenerateCommandHierarchy);
            AddPositiveInt(settings, "MinParameterSetSize", this.MinParameterSetSize);
            AddPositiveInt(settings, "MaxParameterSetSize", this.MaxParameterSetSize);
            AddNonDefaultString(settings, "CommandHierarchySrc", this.CommandHierarchySrc);
            settings.Add("Formatter", this.Formatter);
            settings.Add("DisplayReasonForDisabledItems", this.DisplayReasonForDisabledItems);
            settings.Add("HorizontalSecurityEnabled", this.HorizontalSecurityEnabled);

            return settings;
        }

        private static void AddNonDefaultVal<T>(IDictionary<string, object> settings, string key, T val)
        {
            bool isDefault = val.Equals(default(T));
            if (!isDefault)
                settings.Add(key, val);
        }

        private static void AddNonDefaultPositiveInt(IDictionary<string, object> settings, string key, int val)
        {
            if (val > 0)
                settings.Add(key, val);
        }

        private static void AddPositiveInt(IDictionary<string, object> settings, string key, int val)
        {
            if (val >= 0)
                settings.Add(key, val);
        }

        private static void AddNonDefaultString(IDictionary<string, object> settings, string key, string val)
        {
            if (!string.IsNullOrEmpty(val))
                settings.Add(key, val);
        }

        /// <summary>
        /// Applies <see cref="ICommandDisplay"/> settings to the current instance.
        /// </summary>
        /// <param name="settings">Settings collection which was created by the same or compatibile
        /// <see cref="ICommandDisplay"/> implementation as the current instance.</param>
        public virtual void ApplySettings(IDictionary<string, object> settings)
        {
            foreach (KeyValuePair<string, object> namedValue in settings)
            {
                object val = namedValue.Value;
                if (val == null)
                    continue;

                PropertyInfo property = GetType().GetProperty(namedValue.Key);
                if (property != null)
                {
                    if (property.CanWrite)
                    {
                        if (property.PropertyType.IsAssignableFrom(val.GetType()))
                            property.SetValue(this, val, null);
                    }
                    else
                    {
                        object currPropertyValue = property.GetValue(this, null);
                        if (currPropertyValue is FontInfo && val is FontInfo)
                            (currPropertyValue as FontInfo).CopyFrom(val as FontInfo);
                    }
                }
            }
        }

        #endregion

        #region Overriden WebControl properties mapped to Table properties.

        /// <summary>
        /// Gets or sets the background color of the Web server control. 
        /// </summary>
        [Category("Appearance")]
        [Description("Background color of the Web server control.")]
        public override System.Drawing.Color BackColor
        {
            get
            {
                return this.tblContainer.BackColor;
            }
            set
            {
                this.tblContainer.BackColor = value;
            }
        }

        /// <summary>
        /// Gets or sets the border color of the Web control. 
        /// </summary>
        [Category("Appearance")]
        [Description("Border color of the Web control.")]
        public override System.Drawing.Color BorderColor
        {
            get
            {
                return this.tblContainer.BorderColor;
            }
            set
            {
                this.tblContainer.BorderColor = value;
            }
        }

        /// <summary>
        /// Gets or sets the border style of the Web server control. 
        /// </summary>
        [Category("Appearance")]
        [Description("Border style of the Web server control.")]
        public override BorderStyle BorderStyle
        {
            get
            {
                return this.tblContainer.BorderStyle;
            }
            set
            {
                this.tblContainer.BorderStyle = value;
            }
        }

        /// <summary>
        /// Gets or sets the border width of the Web server control. 
        /// </summary>
        [Category("Appearance")]
        [Description("Border width of the Web server control.")]
        public override Unit BorderWidth
        {
            get
            {
                return this.tblContainer.BorderWidth;
            }
            set
            {
                this.tblContainer.BorderWidth = value;
            }
        }

        /// <summary>
        /// Gets or sets the Cascading Style Sheet (CSS) class rendered by the Web server control on the client.
        /// </summary>
        [Category("Appearance")]
        [Description("CSS class rendered by the Web server control on the client.")]
        public override string CssClass
        {
            get
            {
                return this.tblContainer.CssClass;
            }
            set
            {
                this.tblContainer.CssClass = value;
            }
        }

        /// <summary>
        /// Gets the font properties associated with the Web server control.
        /// </summary>
        [Category("Appearance")]
        [Description("Font properties associated with the Web server control.")]
        public override FontInfo Font
        {
            get
            {
                return this.tblContainer.Font;
            }
        }

        /// <summary>
        /// Gets or sets the foreground color (typically the color of the text) of the Web server control.
        /// </summary>
        [Category("Appearance")]
        [Description("Foreground color (typically the color of the text) of the Web server control.")]
        public override System.Drawing.Color ForeColor
        {
            get
            {
                return this.tblContainer.ForeColor;
            }
            set
            {
                this.tblContainer.ForeColor = value;
            }
        }

        /// <summary>
        /// Gets or sets the height of the Web server control.
        /// </summary>
        [Category("Layout")]
        [Description("Height of the Web server control.")]
        public override Unit Height
        {
            get
            {
                return this.tblContainer.Height;
            }
            set
            {
                this.tblContainer.Height = value;
            }
        }

        /// <summary>
        /// Gets or sets the width of the Web server control.
        /// </summary>
        [Category("Layout")]
        [Description("Width of the Web server control.")]
        public override Unit Width
        {
            get
            {
                return this.tblContainer.Width;
            }
            set
            {
                this.tblContainer.Width = value;
            }
        }

        #endregion

        #region Table properties.

        /// <summary>
        /// Gets or sets the URL of the background image to display behind the control. 
        /// </summary>
        [Category("Appearance")]
        [Description("URL of the background image to display behind the control.")]
        [DefaultValue("")]
        public virtual string BackImageUrl
        {
            get { return this.tblContainer.BackImageUrl; }
            set { this.tblContainer.BackImageUrl = value; }
        }

        /// <summary>
        /// Gets or sets the amount of space between the created command control and the border a cell which contains it.
        /// </summary>
        [Category("Appearance")]
        [DefaultValue(-1)]
        [Description("Amount of space between the created command control and the border a cell which contains it.")]
        public virtual int CellPadding
        {
            get { return this.tblContainer.CellPadding; }
            set { this.tblContainer.CellPadding = value; }
        }

        /// <summary>
        /// Gets or sets the amount of space between cells that contain the created command controls.
        /// </summary>
        [Category("Appearance")]
        [DefaultValue(-1)]
        [Description("Amount of space between cells that contain the created command controls.")]
        public virtual int CellSpacing
        {
            get { return this.tblContainer.CellSpacing; }
            set { this.tblContainer.CellSpacing = value; }
        }

        /// <summary>
        /// Gets or sets the grid line style to display in the control. 
        /// </summary>
        [Category("Appearance")]
        [DefaultValue(typeof(GridLines), "None")]
        [Description("Grid line style to display in the control.")]
        public virtual GridLines GridLines
        {
            get { return this.tblContainer.GridLines; }
            set { this.tblContainer.GridLines = value; }
        }

        #endregion

        #region Child control properties.

        /// <summary>
        /// Gets or sets the direction in which to render the <b>CommandToolBar</b> control.
        /// </summary>
        /// <remarks>The default is Orientation.Horizontal.</remarks>
        [Category("Layout")]
        [Description("Direction in which to render the CommandToolBar control.")]
        [DefaultValue(typeof(Orientation), "Horizontal")]
        public virtual Orientation Orientation
        {
            get { return this.orientation; }
            set { this.orientation = value; }
        }

        /// <summary>
        /// Gets or sets the default type of buttons generated by the toolbar.
        /// </summary>
        /// <value><see cref="BizElements.Web.ButtonType"/>, default is <see cref="BizElements.Web.ButtonType.Button"/>.</value>
        /// <remarks>This property may be overriden by specifying another value in <see cref="CommandStyle"/> object.</remarks>
        [Category("Appearance")]
        [Description("Specifies the default type of buttons generated by the toolbar.")]
        [DefaultValue(typeof(ButtonType), "Button")]
        public virtual ButtonType ButtonType
        {
            get { return this.buttonType; }
            set { this.buttonType = value; }
        }

        /// <summary>
        /// Gets or sets whether the toolbar will analize the provided <see cref="CommandStyle"/> objects 
        /// to determine which <see cref="BizElements.Web.ButtonType"/> to generate for invidivudual items.
        /// </summary>
        /// <value><b>true</b> to allow auto-detection; <b>false otherwise.</b>. Default is <b>true</b>.</value>
        /// <remarks>The toolbar analizes <see cref="CommandStyle.ExtendedProperties"/> to determine which
        /// <see cref="BizElements.Web.ButtonType"/> to generate. The toolbar tries to find the following 
        /// properties in ExtendedProperties. The order of operations is as specified:
        /// <list type="number">
        /// <item><b>ButtonType</b>. The specified button type is generated.</item>
        /// <item><b>Template</b>. A <see cref="TemplateButton"/> is generated.</item>
        /// <item><b>ImageUrl</b>. A <see cref="ImageButton"/> is generated.</item>
        /// </list>
        /// If no match is found, a button type defined by <see cref="ButtonType"/> property of the
        /// <see cref="CommandToolBar"/> class is generated.</remarks>
        [Category("Behavior")]
        [Description("Indicates whether the toolbar will analize the provided CommandStyle objects to determine which ButtonType to generate.")]
        [DefaultValue(true)]
        public virtual bool AutoDetermineButtonType
        {
            get { return this.autoDetectButtonType; }
            set { this.autoDetectButtonType = value; }
        }

        /// <summary>
        /// Gets or sets the Cascading Style Sheet (CSS) class rendered by the generated <see cref="Button"/> controls.
        /// </summary>
        /// <remarks>This property may be overriden by specifying another value in <see cref="CommandStyle"/> object.</remarks>
        [Category("Appearance")]
        [Description("CSS class rendered by the generated Button controls.")]
        [DefaultValue(null)]
        public virtual string ButtonCssClass
        {
            get { return this.buttonCssClass; }
            set { this.buttonCssClass = value; }
        }

        /// <summary>
        /// Gets or sets the skin to apply to the generated <see cref="Button"/> controls.
        /// </summary>
        [Category("Appearance")]
        [Description("Skin to apply to the generated Button controls.")]
        [DefaultValue(null)]
        public string ButtonSkinID
        {
            get { return buttonSkinID; }
            set { buttonSkinID = value; }
        }

        /// <summary>
        /// Gets or sets the Cascading Style Sheet (CSS) class rendered by the generated <see cref="ImageButton"/> controls.
        /// </summary>
        /// <remarks>This property may be overriden by specifying another value in <see cref="CommandStyle"/> object.</remarks>
        [Category("Appearance")]
        [Description("CSS class rendered by the generated ImageButton controls.")]
        [DefaultValue(null)]
        public virtual string ImageButtonCssClass
        {
            get { return this.imageButtonCssClass; }
            set { this.imageButtonCssClass = value; }
        }

        /// <summary>
        /// Gets or sets the skin to apply to the generated <see cref="ImageButton"/> controls.
        /// </summary>
        [Category("Appearance")]
        [Description("Skin to apply to the generated ImageButton controls.")]
        [DefaultValue(null)]
        public string ImageButtonSkinID
        {
            get { return imageButtonSkinID; }
            set { imageButtonSkinID = value; }
        }

        /// <summary>
        /// Gets or sets the Cascading Style Sheet (CSS) class rendered by the generated <see cref="TemplateButton"/> controls.
        /// </summary>
        /// <remarks>This property may be overriden by specifying another value in <see cref="CommandStyle"/> object.</remarks>
        [Category("Appearance")]
        [Description("CSS class rendered by the generated TemplateButton controls.")]
        [DefaultValue(null)]
        public virtual string TemplateCssClass
        {
            get { return this.templateButtonCssClass; }
            set { this.templateButtonCssClass = value; }
        }

        /// <summary>
        /// Gets or sets the HTML template used by the generated <see cref="TemplateButton"/> controls. 
        /// </summary>
        /// <remarks>This property may be overriden by specifying another value in <see cref="CommandStyle"/> object.</remarks>
        [Description("HTML template/format used by the generated TemplateButton controls.")]
        [Category("Behavior")]
        [DefaultValue(null)]
        public virtual string Template
        {
            get { return this.templateButtonDefinition; }
            set { this.templateButtonDefinition = value; }
        }

        /// <summary>
        /// Gets or sets semicolon delimitted <see cref="TemplateButton"/> property names whose values are formatted as defined by <see cref="Template"/>.
        /// </summary>
        /// <remarks>This property may be overriden by specifying another value in <see cref="CommandStyle"/> object.</remarks>
        [Description("Semicolon delimitted TemplateButton properties which are formatted as defined by Template.")]
        [Category("Behavior")]
        [DefaultValue(null)]
        public virtual string TemplateArguments
        {
            get { return this.templateButtonArguments; }
            set { this.templateButtonArguments = value; }
        }

        /// <summary>
        /// Gets or sets optional client side scripts which are used in <see cref="Template"/>.
        /// </summary>
        /// <remarks>This property may be overriden by specifying another value in <see cref="CommandStyle"/> object.</remarks>
        [Description("Client side scripts which are used in Template.")]
        [Category("Behavior")]
        [DefaultValue(null)]
        public virtual string TemplateScript
        {
            get { return this.templateButtonScript; }
            set { this.templateButtonScript = value; }
        }

        /// <summary>
        /// Gets or sets semicolon delimitted JavaScript URLs/files which Template requests to be included in the page.
        /// </summary>
        /// <remarks>This property may be overriden by specifying another value in <see cref="CommandStyle"/> object.</remarks>
        [Description("Semicolon delimitted JavaScript URLs/files which should be included in the page.")]
        [Category("Behavior")]
        [DefaultValue(null)]
        public virtual string TemplateScriptIncludeUrls
        {
            get { return this.templateButtonScriptIncludeUrls; }
            set { this.templateButtonScriptIncludeUrls = value; }
        }

        /// <summary>
        /// Gets or sets the skin to apply to the generated <see cref="TemplateButton"/> controls.
        /// </summary>
        [Category("Appearance")]
        [Description("Skin to apply to the generated TemplateButton controls.")]
        [DefaultValue(null)]
        public string TemplateButtonSkinID
        {
            get { return templateButtonSkinID; }
            set { templateButtonSkinID = value; }
        }

        #endregion

        #region Nonbrowsable properties - do not end up in settings.

        /// <summary>
        /// Gets or sets command hierarchy which defines how <see cref="ICommand"/> objects are grouped and displayed in UI.
        /// </summary>
        /// <remarks>Ignores entries for commands which are not present in the <see cref="Commands"/> collection.</remarks>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual CommandGroupStyle CommandHierarchy
        {
            get { return this.commandHierarchy; }
            set { this.commandHierarchy = value; }
        }

        /// <summary>
        /// Gets or sets a collection of commands which are to be displayed in the <b>CommandToolBar</b>.
        /// </summary>
        /// <remarks>Commands which are not defined in the <see cref="CommandHierarchy"/> will not be displayed.</remarks>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual IList<ICommand> Commands
        {
            get { return this.commands; }
            set { this.commands = value; }
        }        

        /// <summary>
        /// Gets or sets the actor which is using the displayed commands. Nullable.
        /// </summary>
        /// <remarks>When set, the <b>CommandToolBar</b> runs security checks to see which of the commands 
        /// provided in the <see cref="Commands"/> collection are available to the actor.</remarks>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual IActor Actor
        {
            get { return this.actor; }
            set { this.actor = value; }
        }

        /// <summary>
        /// Gets or sets the <see cref="IMessageFormatter"/> object used to format values in the result grid.
        /// </summary>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual IMessageFormatter Formatter
        {
            get { return this.formatter; }
            set { this.formatter = value; }
        }

        /// <summary>
        /// Gets value indicating whether the AJAX dependent features of the <b>CommandToolBar</b> are enabled.
        /// </summary>
        /// <value><b>true</b> if the <b>Page</b> to which the control belongs to contains a <b>ScriptManager</b> control; <b>false</b> otherwise.</value>
        /// <remarks>The <b>CommandToolBar</b> will automatically disable all features which use AJAX extensions, 
        /// such as horizontal security enforcing, if the <b>ScriptManager</b> control is not present.</remarks>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public bool AjaxEnabled
        {
            get { return this.ajaxEnabled; }
        }

        const string enableAllItemsFunctionCall = "Biz_CommandToolBar_EnableAll('{0}', {1}); ";

        /// <summary>
        /// Gets the JavaScript code (function call) that enables all commands available to actor.
        /// </summary>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual string EnableAllCommandsScript
        {
            get
            {
                EnsureChildControls();
                string script = string.Format(enableAllItemsFunctionCall, this.ClientID, this.Commands.Count);
                return script;
            }
        }

        #endregion        

        #region Toolbar properties.

        bool autoGenerateCommandHierarchy = true;

        /// <summary>
        /// Gets or sets whether the <see cref="CommandHierarchy"/> items are automatically generated in
        /// <b>Render</b> stage based on the command information stored in <see cref="Commands"/>.
        /// </summary>
        /// <value><b>true</b> if command items are automatically created. Default is <b>true</b>.</value>
        /// <remarks>Automatic generation in rendering stage occurs only if the <see cref="CommandHierarchy"/> 
        /// is empty. Only items for commands that match <see cref="MinParameterSetSize"/> and
        /// <see cref="MaxParameterSetSize"/> criteria are generated.</remarks>
        [Category("Behavior")]
        [DefaultValue(true)]
        [Description("Indicates whether CommandHierarchy items are automatically generated.")]
        public virtual bool AutoGenerateCommandHierarchy
        {
            get { return this.autoGenerateCommandHierarchy; }
            set { this.autoGenerateCommandHierarchy = value; }
        }

        int minParameterSetSize = -1;

        /// <summary>
        /// Gets or sets the minimum parameter-set size of the commands which are to be displayed in the toolbar.
        /// </summary>
        /// <value>Positive number which specifies the minimum parameter-set size or a negative value if no
        /// constraints should be applied. Default is <b>-1</b> indicating that all defined commands
        /// are to be displayed.</value>
        [Category("Behavior")]
        [DefaultValue(-1)]
        [Description("Minimum parameter-set size of the commands which are to be displayed in the toolbar.")]
        public virtual int MinParameterSetSize
        {
            get { return this.minParameterSetSize; }
            set { this.minParameterSetSize = value; }
        }

        int maxParameterSetSize = -1;

        /// <summary>
        /// Gets or sets the maximum parameter-set size of the commands which are to be displayed in the toolbar.
        /// </summary>
        /// <value>Positive number which specifies the maximum parameter-set size or a negative value if no
        /// constraints should be applied. Default is <b>-1</b> indicating that all defined commands
        /// are to be displayed.</value>
        [Category("Behavior")]
        [DefaultValue(-1)]
        [Description("Maximum parameter-set size of the commands which are to be displayed in the toolbar.")]
        public virtual int MaxParameterSetSize
        {
            get { return this.maxParameterSetSize; }
            set { this.maxParameterSetSize = value; }
        }

        string commandHierarchySrc;

        /// <summary>
        /// Gets or sets the URL of the XML file which is read by <see cref="CommandHierarchy"/>.
        /// </summary>
        [Category("Behavior")]
        [Editor(typeof(XmlUrlEditor), typeof(UITypeEditor))]
        [Description("URL of the XML file which is read by CommandHierarchy.")]
        [DefaultValue(null)]
        public virtual string CommandHierarchySrc
        {
            get { return this.commandHierarchySrc; }
            set { this.commandHierarchySrc = value; }
        }

        /// <summary>
        /// Gets or sets whether the reason why an item is disabled is displayed in a tooltip.
        /// </summary>
        /// <value><b>true</b> if business rules which prevent the execution of the command are displayed; 
        /// <b>false</b> otherwise. Default is <b>true</b>.</value>
        /// <remarks>The reason is obtained by executing the <see cref="ICommand.GrantExecute"/> method.</remarks>
        [Category("Appearance")]
        [DefaultValue(true)]
        [Description("Whether the reason why an item is disabled is displayed in a tooltip.")]
        public virtual bool DisplayReasonForDisabledItems
        {
            get { return this.displayReasonForDisabledItems; }
            set { this.displayReasonForDisabledItems = value; }
        }

        /// <summary>
        /// Gets or sets whether the <b>CommandToolBar</b> will automatically enforce horizontal security, 
        /// ie. check which commands can be executed.
        /// </summary>
        /// <value><b>true</b> if the <b>CommandToolBar</b> should disable or hide the commands which cannot
        /// be executed; <b>false</b> otherwise. Default is <b>true</b>.</value>
        [Category("Behavior")]
        [DefaultValue(true)]
        [Description("Whether the CommandToolBar will automatically enforce horizontal security.")]
        public virtual bool HorizontalSecurityEnabled
        {
            get { return this.horizontalSecurityEnabled; }
            set { this.horizontalSecurityEnabled = value; }
        }

        #endregion
    }

    /// <summary>
    /// Performs design-time rendering of a <see cref="CommandToolBar"/> control.
    /// </summary>
    internal class CommandToolBarDesigner : ControlDesigner
    {
        #region GetDesignTimeHtml.

        /// <summary>
        /// Retrieves the HTML markup that is used to represent the control at design time.
        /// </summary>
        /// <returns>The HTML markup used to represent the control at design time.</returns>
        public override string GetDesignTimeHtml()
        {
            CommandToolBar toolbar = new CommandToolBar();
            ReflectionUtil.CopyPublicProperties(this.Component, toolbar);

            toolbar.Visible = true;            
            toolbar.Commands = new List<ICommand>();
            toolbar.Commands.Add(new JsCommand("cmd1", "Do stuff", "alert('Do stuff');"));
            toolbar.Commands.Add(new JsCommand("cmd2", "Do something else", "alert('Do something else');"));
            toolbar.CommandHierarchy = new CommandGroupStyle();
            toolbar.CommandHierarchy.Items.Add(new CommandGroupItem(new CommandStyle("cmd1", null, null, CommandStyle.DisableStyle.Automatic, CommandStyle.DisableStyle.Automatic)));
            toolbar.CommandHierarchy.Items.Add(new CommandGroupItem(new CommandStyle("cmd2", null, null, CommandStyle.DisableStyle.Automatic, CommandStyle.DisableStyle.Automatic)));
            string designTimeHtml = toolbar.GetDesignTimeHtml();

            return designTimeHtml;
        }

        #endregion
    }    
}
